<?php

namespace EsBuilder;

class EGrammar
{
    /**
     * @var array
     */
    protected $selectComponents = [
        '_source' => 'columns',
        'query' => 'wheres',
        'sort' => 'orders',
        'size' => 'limit',
        'from' => 'offset',
        'index' => 'index',
        'type' => 'type',
        'aggs' => ['groupBy', 'aggs'],
        'highlight' => 'highlight',
    ];

    public function compileIndex(EBuilder $builder): string
    {
        return $builder->index;
    }

    public function compileType(EBuilder $builder): string
    {
        return $builder->type;
    }

    public function compileColumns(EBuilder $builder): array
    {
        return $builder->columns;
    }

    public function compileOrders(EBuilder $builder): array
    {
        return $builder->orders;
    }

    public function compileOffset(EBuilder $builder): int
    {
        return $builder->offset;
    }

    public function compileLimit(EBuilder $builder): int
    {
        return $builder->limit;
    }

    public function compileSelect(EBuilder $builder)
    {
        return $builder->columns;
    }

    public function compileWheres(EBuilder $builder)
    {
        $whereGroups = $this->getWhereGroup($builder->wheres);

        $firstLevel = [];
        foreach ($whereGroups as $whereGroup) {
            $group = [];
            foreach ($whereGroup as $where) {
                if ($where['type'] == 'Nested') {
                    $group['bool']['must'][] = $this->compileWheres($where['query']);
                } elseif ($where['type'] == 'Not') {
                    $group['bool']['must_not'][] = $this->whereLeaf($where['leaf'], $where['column'], $where['value'], $where['operator']);
                } else {
                    $group['bool']['must'][] = $this->whereLeaf($where['leaf'], $where['column'], $where['value'], $where['operator']);
                }
            }

            if (count($whereGroups) == 1) {
                $firstLevel = $group;
            } else {
                $firstLevel['bool']['should'][] = $group;
            }
        }
        return $firstLevel;
    }

    public function compileHighlight(EBuilder $builder)
    {
        return $builder->highlight;
    }

    public function compileAggs(EBuilder $builder)
    {
        return $builder->aggs;
    }

    public function compileGroupBy(EBuilder $builder, $aggsList = [])
    {
        $aggsList = $aggsList ?: $builder->groupBy;
        $current = array_shift($aggsList);
        if ($current) {
            $columnName = $current['column'];
            $tmpData = [
                $columnName => [
                    'terms' => [
                        'field' => $current['column'],
                        'order' => ['_count' => $current['sortDirection']]
                    ]
                ]
            ];
            if ($aggsList) {
                $tmpData[$columnName]['aggs'] = $this->compileGroupBy($builder, $aggsList);
            }
        }

        return $tmpData ?? [];
    }

    /**
     * 将where条件按照and和or分组
     * @param array $wheres
     * @return array
     */
    protected function getWhereGroup(array $wheres): array
    {
        // 查询所有or条件在wheres中的索引
        $orIndexArr = (array)array_keys(array_map(function ($where) {
            return $where['boolean'];
        }, $wheres), 'or');

        $group = [];
        $lastIndex = 0;
        foreach ($orIndexArr as $orIndex) {
            $group[] = array_slice($wheres, $lastIndex, $orIndex - $lastIndex);
            $lastIndex = $orIndex;
        }

        $group[] = array_slice($wheres, $lastIndex);

        return $group;
    }

    protected function whereLeaf(string $leaf, string $column, $value, string $operator = null): array
    {
        if ($leaf === 'range') {
            return [
                $leaf => [$column => is_array($value) ? $value : [$operator => $value]]
            ];
        } else {
            return [
                $leaf => [$column => $value]
            ];
        }
    }

    public function compileComponents(EBuilder $builder)
    {
        $body = [];
        foreach ($this->selectComponents as $key => $components) {
            if (!is_array($components)) {
                $components = [$components];
            }
            foreach ($components as $component) {
                if (!is_null($builder->$component)) {
                    $method = 'compile' . ucfirst($component);
                    $body[$key] = $this->$method($builder);
                }
            }
        }
        return $body;
    }

    public function compileQuery(EBuilder $builder)
    {
        $body = $this->compileComponents($builder);
        $index = array_pull($body, 'index');
        $type = array_pull($body, 'type');

        return ['index' => $index, 'type' => $type, 'body' => $body];
    }

    public function compileCreate(EBuilder $builder, $id, array $data)
    {
        return array_merge($this->compileComponents($builder), [
            'id' => $id,
            'body' => $data,
        ]);
    }

    public function compileUpdate(EBuilder $builder, $id, array $data)
    {
        return array_merge($this->compileComponents($builder), [
            'id' => $id,
            'body' => ['doc' => $data],
        ]);
    }

    public function compileDelete(EBuilder $builder, $id)
    {
        return array_merge($this->compileComponents($builder), ['id' => $id]);
    }
}
